﻿using System;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Imaging;
using RaytracerLib.MathLib;
using TomanuExtensions;
using RaytracerLib.Layers.Interpolators;
using RaytracerLib.Layers.OverlayCorrectors;
using RaytracerLib.Resamplers;
using System.Collections.Generic;
using RaytracerLib.Resamplers.Filters;

namespace RaytracerLib.Layers
{
    public class ColorArrayFloat : ColorArrayBase
    {
        protected internal float[] m_values;

        protected ColorArrayFloat(OverlayMethod a_overlayMethod = OverlayMethod.Overlap,
            InterpolationMethod a_interpolationMethod = InterpolationMethod.Bilinear)
            : base(a_overlayMethod, a_interpolationMethod)
        {
        }

        public ColorArrayFloat(int a_width, int a_height,
            OverlayMethod a_overlayMethod = OverlayMethod.Overlap,
            InterpolationMethod a_interpolationMethod = InterpolationMethod.Bilinear)
            : base(a_overlayMethod, a_interpolationMethod)
        {
            Width = a_width;
            Height = a_height;
            m_values = new float[Width * Height * 3];
        }

        public ColorArrayFloat(Bitmap a_bmp,
            OverlayMethod a_overlayMethod = OverlayMethod.Overlap,
            InterpolationMethod a_interpolationMethod = InterpolationMethod.Bilinear)
            : base(a_overlayMethod, a_interpolationMethod)
        {
            Create(a_bmp);
        }

        public ColorArrayFloat(ColorArrayBase a_src)
            : this(a_src.Width, a_src.Height, a_src.OverlayMethod, a_src.InterpolationMethod)
        {
            this.Draw(a_src, new Rectangle(0, 0, Width, Height), new Rectangle(0, 0, Width, Height));
        }

        protected void Create(Bitmap a_bmp)
        {
            Width = a_bmp.Width;
            Height = a_bmp.Height;
            m_values = new float[Width * Height * 3];

            System.Drawing.Imaging.BitmapData bd = a_bmp.LockBits(
                new Rectangle(0, 0, Width, Height),
                ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);

            try
            {
                IntPtr ptr = bd.Scan0;
                int bytes = bd.Stride * a_bmp.Height;
                byte[] values = new byte[bytes];
                System.Runtime.InteropServices.Marshal.Copy(ptr, values, 0, bytes);

                for (int y = 0; y < Height; y++)
                {
                    int src_index = y * bd.Stride;
                    int dest_index = y * Width * 3;

                    for (int x = 0; x < Width; x++)
                    {
                        m_values[dest_index++] = (float)Gamma.SRGBToLinear(values[src_index++]);
                        m_values[dest_index++] = (float)Gamma.SRGBToLinear(values[src_index++]);
                        m_values[dest_index++] = (float)Gamma.SRGBToLinear(values[src_index++]);
                    }
                }
            }
            finally
            {
                a_bmp.UnlockBits(bd);
            }
        }

        public ColorArrayFloat GetArrayRect(Rectangle a_rect)
        {
            ColorArrayFloat dest = new ColorArrayFloat(a_rect.Width, a_rect.Height);
            dest.Draw(this, a_rect, new Rectangle(0, 0, a_rect.Width, a_rect.Height));
            return dest;
        }

        public override Bitmap GetBitmap()
        {
            return GetBitmap(new Rectangle(0, 0, Width, Height));
        }

        public override Bitmap GetBitmap(Rectangle a_rect)
        {
            Bitmap bmp = new Bitmap(a_rect.Width, a_rect.Height);

            var rect = new Rectangle(0, 0, Width, Height);
            rect.Intersect(a_rect);
            Debug.Assert(rect == a_rect);

            System.Drawing.Imaging.BitmapData bd = bmp.LockBits(
                new Rectangle(0, 0, a_rect.Width, a_rect.Height),
                ImageLockMode.WriteOnly, PixelFormat.Format24bppRgb);

            try
            {
                byte[] values = new byte[a_rect.Height * bd.Stride];

                for (int y = a_rect.Top; y < a_rect.Bottom; y++)
                {
                    int dest_index = (y - a_rect.Top) * bd.Stride;

                    for (int x = a_rect.Left; x < a_rect.Right; x++)
                    {
                        Color c = Gamma.LinearToSRGB(GetColor(x, y)).SystemColor;
          
                        values[dest_index++] = c.B;
                        values[dest_index++] = c.G;
                        values[dest_index++] = c.R;
                    }
                }

                System.Runtime.InteropServices.Marshal.Copy(values, 0,
                    bd.Scan0, values.Length);
            }
            finally
            {
                bmp.UnlockBits(bd);
            }

            return bmp;
        }

        public override ColorFloat GetColor(int a_x, int a_y)
        {
            int index = (a_y * Width + a_x) * 3;

            float b = m_values[index++];
            float g = m_values[index++];
            float r = m_values[index++];

            return new ColorFloat(r, g, b);
        }

        public override void SetColor(int a_x, int a_y, ColorFloat a_color)
        {
            int index = (a_y * Width + a_x) * 3;

            m_values[index++] = a_color.B;
            m_values[index++] = a_color.G;
            m_values[index++] = a_color.R;
        }

        public override double GetValue(int a_x, int a_y)
        {
            return Gamma.LinearToSRGB(GetColor(a_x, a_y)).Avg;
        }

        public override void SetValue(int a_x, int a_y, double a_value)
        {
            int index = (a_y * Width + a_x) * 3;
            float value = (float)Gamma.SRGBToLinear(a_value);

            Debug.Assert(value >= 0);

            m_values[index++] = value;
            m_values[index++] = value;
            m_values[index] = value;
        }

        public virtual bool EqualsContent(ColorArrayFloat a_ar)
        {
            if (Width != a_ar.Width)
                return false;
            if (Height != a_ar.Height)
                return false;

            for (int x = 0; x < Width; x++)
            {
                for (int y = 0; y < Height; y++)
                {
                    if (GetColor(x, y) != a_ar.GetColor(x, y))
                        return false;
                }
            }

            return true;
        }

        public override bool Equals(object a_obj)
        {
            if (Object.ReferenceEquals(a_obj, this))
                return true;

            if (!base.Equals(a_obj))
                return false;

            ColorArrayFloat ar = a_obj as ColorArrayFloat;
            if (ar == null)
                return false;

            return EqualsContent(ar);
        }

        public override int GetHashCode()
        {
            return base.GetHashCode();
        }

        public void Resize(ColorArrayFloat a_dest, FilterType a_filter)
        {
            List<List<PixelWeight>> horz_weights, vert_weights;
            ResizerResampler.PrecalculateWeights(Width, Height, a_dest.Width, a_dest.Height, 
                out horz_weights, out vert_weights, Filter.Create(a_filter));

            ColorArrayFloat temp_bd = new ColorArrayFloat(a_dest.Width, Height);

            for (int x = 0; x < temp_bd.Width; x++)
            {
                List<PixelWeight> weights = horz_weights[x];

                for (int y = 0; y < temp_bd.Height; y++)
                {
                    ColorFloat color = new ColorFloat();

                    foreach (PixelWeight pw in weights)
                        color += GetColor(pw.Pos, y) * pw.Weight;

                    temp_bd.SetColor(x, y, color.AboveZero);
                }
            }

            for (int y = 0; y < a_dest.Height; y++)
            {
                List<PixelWeight> weights = vert_weights[y];

                for (int x = 0; x < a_dest.Width; x++)
                {
                    ColorFloat color = new ColorFloat();

                    foreach (PixelWeight pw in weights)
                        color += temp_bd.GetColor(x, pw.Pos) * pw.Weight;

                    a_dest.SetColor(x, y, color.AboveZero);
                }
            }
        }
    }
}